﻿using System;
using System.Diagnostics;
using Fine.Collections.Intf;
using Fine.Collections.Views;



namespace Fine.Collections.Cont
{
    /// <summary>
    /// Hash map.
    /// </summary>
    /// <typeparam name="K">type of a key.</typeparam>
    /// <typeparam name="V">type of an assicated value.</typeparam>
    [DebuggerDisplay("Count = {Count}")]
    [DebuggerTypeProxy(typeof(HashMap<,>.DebugView))]
    public class HashMap<K,V> : WMap<K,V>
    {
        /// <summary>
        /// Internal storage.
        /// </summary>
        private readonly HashTable<MapEntry<K,V>> ht;

        /// <summary>
        /// Keys view.
        /// </summary>
        private readonly RSet<K> keys;

        /// <summary>
        /// Values view.
        /// </summary>
        private readonly RCollection<V> values;




        /// <summary>
        /// Trivial constructor.
        /// </summary>
        public HashMap()
            : this(0)
        {}


        /// <summary>
        /// Constructs a map with the specified capacity.
        /// </summary>
        /// <param name="initialCapacity">initial capacity.</param>
        public HashMap(int initialCapacity) 
        {
            ht = new HashTable<MapEntry<K, V>>(initialCapacity);
            keys = new KeysView(this);
            values = new RCollectionAdaptor<HashTable<MapEntry<K,V>>,MapEntry<K,V>,V>(ht, e => e.Value);
        }


        public RSet<K> Keys
        {
            get { return keys; }
        }

        public RCollection<V> Values
        {
            get { return values; }
        }

        public RCollection<RMapEntry<K,V>> Entries
        {
            get { return ht; }
        }


        /// <summary>
        /// Includes the given pair into the map.
        /// The map should not contain a pair with this key.
        /// </summary>
        /// <param name="key">key.</param>
        /// <param name="value">value.</param>
        /// <exception cref="DuplicatesFoundException">if a pair with such key already exists.</exception>
        /// <remarks>
        /// If you want to add/replace operation - use <see cref="this"/> instead.
        /// </remarks>
        public void Include(K key, V value)
        {
            if (ContainsKey(key))
                throw new DuplicatesFoundException("The key <"+key+"> already exists in the map.");

            MapEntry<K,V> entry = new MapEntry<K,V>(key,value);
            InternalAdd(entry);
        }


        /// <summary>
        /// Excludes a pair with the specified key from the map, if such exists.
        /// If no ones - do nothing.
        /// </summary>
        /// <param name="key">key.</param>
        /// <returns>excluded value; or <c>default{T}</c> if no such key in the map.</returns>
        public V Exclude(K key)
        {
            HashChain<MapEntry<K,V>> hchain =
                    ht.FindFirst(key.GetHashCode(),
                                 mapEntry => mapEntry.Key.Equals(key));

            if (hchain.chain != null)
            {
                ht.Remove(hchain);
                return hchain.chain.item.Value;
            }
            else
            {
                return default(V);
            }
        }


        private void InternalAdd(MapEntry<K,V> entry)
        {
            ht.Add(entry, entry.GetHashCode());
        }


        public int Count
        {
            get { return ht.Count; }
        }

        public bool IsEmpty
        {
            get { return ht.IsEmpty; }
        }

        public bool IsNotEmpty
        {
            get { return ht.IsNotEmpty; }
        }

        public void Clear()
        {
            ht.Clear();
        }


        /// <summary>
        /// Looks for the pair with the specified key.
        /// </summary>
        /// <param name="key">key to look for.</param>
        /// <returns>value of the found pair if found.</returns>
        public SearchResult<V> Find (K key)
        {
            HashChain<MapEntry<K, V>> hchain =
                ht.FindFirst(key.GetHashCode(),
                             mapEntry => mapEntry.Key.Equals(key));
            return hchain.chain != null 
                    ? SearchResults<V>.Found(hchain.chain.item.Value) 
                    : SearchResults<V>.NotFound();
        }


        public V this [K key]
        {
            get 
            {
                return Find(key).Item;
            }

            set
            {
                HashChain<MapEntry<K,V>> hchain =
                    ht.FindFirst(key.GetHashCode(),
                                 mapEntry => mapEntry.Key.Equals(key));
                if (hchain.chain != null)
                    hchain.chain.item.Value = value;
                else
                    InternalAdd(new MapEntry<K, V>(key, value));
            }
        }


        public bool ContainsKey(K key)
        {
            int hashCode = key.GetHashCode();
            bool found;
            ht.CheckExistance(hashCode, 
                              mapEntry => mapEntry.Key.Equals(key),
                              out found);
            return found;
        }



        #region KEYS VIEW

        private sealed class KeysView : RCollectionAdaptor<HashTable<MapEntry<K,V>>,MapEntry<K,V>,K>,
                                        RSet<K>
        {
            private readonly HashMap<K,V> hmap;

            internal KeysView(HashMap<K,V> hmap)
                : base(hmap.ht, e => e.Key)
            {
                this.hmap = hmap;
            }


            public bool Contains(K item)
            {
                return hmap.ContainsKey(item);
            }
        }

        #endregion



        #region DEBUG HELPER

        /// <summary>
        /// Debug helper.
        /// </summary>
        internal class DebugView
        {
            private readonly HashTable<MapEntry<K,V>> ht;


            public DebugView(HashMap<K,V> map)
            {
                this.ht = map.ht;
            }


            [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
            public Array Items
            {
                get
                {
                    return ht != null 
                        ? DebugViewUtils.MakeArray(ht.ToArray(), 1, ht.Count) 
                        : new MapEntry<K,V>[0];
                }
            }
        }

        #endregion

    }


}